import asyncio
import subprocess
import sys
from concurrent.futures import ThreadPoolExecutor
from threading import Lock
from typing import Union, List, Optional

from exceptions.unzip_excepotion import TerminationError, CompressionError
from interface.run_command_interfaces import ExecutorInterface


class ExecutorMixin:

    def __init__(self, max_workers: int = 4):
        self.current_process: Union[subprocess.Popen, asyncio.subprocess.Process, None] = None
        self._manually_terminated = False
        self._process_lock = Lock()
        self.thread_executor = ThreadPoolExecutor(max_workers=max_workers)
        self._default_encoding = sys.getdefaultencoding()  # 动态获取系统编码

    def _handle_stdout_stderr(
            self,
            stdout: str,
            stderr: str,
            returncode: Optional[int],
            command: List[str]
    ) -> str:
        if self._manually_terminated:
            raise TerminationError("命令被手动终止")

        if returncode is None:
            raise CompressionError(f"命令未返回状态码: {' '.join(command)}")

        if returncode != 0:
            error_msg = (
                f"命令执行失败 [code={returncode}]: {' '.join(command)}\n"
                f"错误输出: {stderr.strip() or '<无输出>'}"
            )
            raise CompressionError(error_msg)

        return stdout.strip()

    def __del__(self):
        """确保线程池资源回收"""
        self.thread_executor.shutdown(wait=False)


class DefaultExecutor(ExecutorMixin, ExecutorInterface):
    def __init__(self, max_workers: int = 4):
        super().__init__(max_workers)

    def run_command(self, command: List[str]) -> str:
        try:
            with self._process_lock:
                self._manually_terminated = False
                self.current_process = subprocess.Popen(
                    command,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                    encoding=self._default_encoding,  # 使用动态编码
                    errors="replace"
                )

            stdout, stderr = self.current_process.communicate()
            return self._handle_stdout_stderr(
                stdout,
                stderr,
                self.current_process.returncode,
                command
            )

        except FileNotFoundError as e:
            raise CompressionError(f"命令不存在: {command[0]}") from e
        except subprocess.SubprocessError as e:
            if self._manually_terminated:
                raise TerminationError("命令终止") from e
            raise CompressionError(f"子进程错误: {e}") from e
        finally:
            with self._process_lock:
                self.current_process = None

    async def async_run_command(self, command: List[str]) -> str:
        try:
            with self._process_lock:
                self._manually_terminated = False
                self.current_process = await asyncio.create_subprocess_exec(
                    *command,
                    stdout=asyncio.subprocess.PIPE,
                    stderr=asyncio.subprocess.PIPE
                )

            stdout, stderr = await self.current_process.communicate()

            # 统一使用系统编码解码
            decoded_stdout = stdout.decode(self._default_encoding, errors="replace") if stdout else ""
            decoded_stderr = stderr.decode(self._default_encoding, errors="replace") if stderr else ""

            return self._handle_stdout_stderr(
                decoded_stdout,
                decoded_stderr,
                self.current_process.returncode,
                command
            )

        except FileNotFoundError as e:
            raise CompressionError(f"命令不存在: {command[0]}") from e
        except OSError as e:  # 更通用的系统错误
            if self._manually_terminated:
                raise TerminationError("命令终止") from e
            raise CompressionError(f"系统错误: {e}") from e
        finally:
            with self._process_lock:
                self.current_process = None

    def run_in_thread(self, command: List[str]):
        return self.thread_executor.submit(self.run_command, command)

    def terminate_process(self):
        with self._process_lock:
            if not self.current_process:
                return

            self._manually_terminated = True
            try:
                if isinstance(self.current_process, subprocess.Popen):
                    self.current_process.terminate()
                    try:
                        self.current_process.wait(timeout=2)
                    except subprocess.TimeoutExpired:
                        self.log.warning("进程强制终止")
                        self.current_process.kill()
                        self.current_process.wait()

                elif isinstance(self.current_process, asyncio.subprocess.Process):
                    self.current_process.terminate()
                    # 异步进程需要特殊处理等待
                    if self.current_process.returncode is None:
                        self.log.warning("等待异步进程终止")
                        # 在事件循环中安排等待任务
                        asyncio.create_task(self._wait_async_process())
            except ProcessLookupError:
                self.log.debug("进程已终止")
            except Exception as e:
                self.log.error(f"终止错误: {e}", exc_info=True)

    async def _wait_async_process(self):
        """等待异步进程退出（防止僵尸进程）"""
        if isinstance(self.current_process, asyncio.subprocess.Process):
            try:
                await self.current_process.wait()
            except Exception as e:
                self.log.debug(f"进程等待异常: {e}")


if __name__ == '__main__':
    executor = DefaultExecutor()
    p = [
        'bandizip', 'x', '-y', '-p:123456',
        '-o:E:\\myproject\\python\\extract-script\\log',
        'E:\\myproject\\python\\extract-script\\log\\unzip.7z'
    ]
    print(" ".join(p))
    print(executor.run_command(p))
